package org.zoomdev.zoom.dao.driver.mysql;

import org.apache.commons.lang3.StringUtils;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.filter.Filter;
import org.zoomdev.zoom.dao.Entity;
import org.zoomdev.zoom.dao.EntityField;
import org.zoomdev.zoom.dao.driver.AbsDriver;
import org.zoomdev.zoom.dao.impl.*;
import org.zoomdev.zoom.dao.meta.ColumnMeta;
import org.zoomdev.zoom.dao.meta.DefaultValue;

import java.sql.Types;
import java.util.*;
import java.util.Map.Entry;

public class MysqlDriver extends AbsDriver {


    public static final String InnoDB = "InnoDB";
    public static final String MyISAM = "MyISAM";
    public static final String Memory = "Memory";
    public static final String NDB = "NDB";
    public static final String Archive = "Archive";
    public static final String Blackhole = "Blackhole";
    public static final String Federated = "Federated";


    public static final String CURRENT_TIMESTAMP = "CURRENT_TIMESTAMP";
    public static final String CURRENT_TIMESTAMP_ON_UPDATE = "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP";

    public static final DefaultValue FUNC_CURRENT_TIMESTAMP = DefaultValue.function(CURRENT_TIMESTAMP);
    public static final DefaultValue FUNC_CURRENT_TIMESTAMP_ON_UPDATE = DefaultValue.function(CURRENT_TIMESTAMP_ON_UPDATE);

    @Override
    public StringBuilder buildLimit(
            StringBuilder sql,
            List<Object> values,
            int position, int size) {


        values.add(position);
        values.add(size);
        return sql.append(" LIMIT ?,?");

    }

    @Override
    public String formatColumnType(ColumnMeta columnMeta) {
        int type = columnMeta.getType();
        switch (type) {
            case Types.INTEGER:
                if (columnMeta.isUnsigned()) {
                    return "INT UNSIGNED";
                }
                return "INT(11)";
            case Types.SMALLINT:
                if (columnMeta.isUnsigned()) {
                    return "SMALLINT UNSIGNED";
                }
                return "SMALLINT(11)";
            case Types.BIGINT:
                if (columnMeta.isUnsigned()) {
                    return "BIGINT UNSIGNED";
                }
                return "BIGINT(11)";
            case Types.TINYINT:
                if (columnMeta.isUnsigned()) {
                    return "TINYINT UNSIGNED";
                }
                return "TINYINT";
            case Types.VARCHAR:
                return "VARCHAR(" + columnMeta.getMaxLen() + ")";
            case Types.NVARCHAR:
                return "NVARCHAR(" + columnMeta.getMaxLen() + ")";
            case Types.DATE:
                return "DATE";
            case Types.TIME:
                return "TIME";
            case Types.BOOLEAN:
                return "TINYINT(1)";
            case Types.TIMESTAMP:
                return "TIMESTAMP";
            case ZoomDatabaseBuilder.DATETIME:
                return "DATETIME";
            case Types.CHAR:
                return "CHAR(" + columnMeta.getMaxLen() + ")";
            case Types.CLOB:
                if (columnMeta.getMaxLen() == 65535) {
                    return "TEXT";
                }
                return "MEDIUMTEXT";
            case Types.NUMERIC:
            case Types.DOUBLE:
                return "DOUBLE";
            case Types.BLOB:
                if (columnMeta.getSubType() == ColumnMeta.MEDIUM) {
                    return "MEDIUMBLOB";
                }
                return "BLOB";
            case Types.DECIMAL:
                return String.format("DECIMAL(%d,%d)", columnMeta.n, columnMeta.m);

            default:
                throw new ZoomException("不支持的类型" + columnMeta.getType());
        }

    }

    public void insertOrUpdate(StringBuilder sb, List<Object> values, String tableName, Map<String, Object> data, String... unikeys) {
        StringBuilder signs = new StringBuilder();
        sb.append("INSERT INTO ")
                .append(tableName)
                .append(" (");
        boolean first = true;
        for (Entry<String, Object> entry : data.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (first) {
                first = false;
            } else {
                signs.append(',');
                sb.append(',');
            }
            sb.append(key);
            signs.append('?');
            values.add(value);
        }
        sb.append(") VALUES (").append(signs).append(") ON DUPLICATE KEY UPDATE ");
        first = true;
        Set<String> keySet = new HashSet<String>();
        Collections.addAll(keySet, unikeys);
        for (Entry<String, Object> entry : data.entrySet()) {
            String key = entry.getKey();
            if (keySet.contains(key)) {
                continue;
            }
            if (first) {
                first = false;
            } else {
                sb.append(',');
            }
            sb.append(key).append("=?");
            values.add(entry.getValue());
        }
    }

    @Override
    public String buildDropIfExists(String table) {
        StringBuilder sb = new StringBuilder();
        sb.append("DROP TABLE IF EXISTS ");
        protectTable(sb, table);
        return sb.toString();
    }

    @Override
    public void buildTable(TableBuildInfo table, List<String> sqlList) {

        sqlList.add(buildCreateTable(table));
        //index
        buildIndex(table, sqlList);

        buildUnique(table, sqlList);
    }

    @Override
    public String protectTable(String tableName) {
        TableSource s = TableSource.from(tableName);
        if (s.alias != null) {
            return tableName;
        }
        return protectColumn(tableName);
    }

    @Override
    public String getTableCatFromUrl(String url) {
        //jdbc:mysql://SERVER:PORT/DBNAME?useUnicode=true&characterEncoding=UTF-8
        int n = url.lastIndexOf("?");
        if (n > 0) {
            url = url.substring(0, n);
        }

        return url.substring(url.lastIndexOf("/") + 1);
    }

    @Override
    public <T> void buildInsertOrUpdate(SimpleSqlBuilder builder,
                                        Entity entity,
                                        T data,
                                        Filter<EntityField> filter,
                                        boolean ignoreNull,
                                        String[] unikeys) {
        EntitySqlUtils.buildInsertOrUpdateForMysql(
                builder.rawSql(),
                builder.values(),
                builder.adapters(),
                this,
                entity,
                data,
                filter,
                ignoreNull,
                unikeys);

    }

    protected String protectName(String name) {
        return protectName(new StringBuilder(), name).toString();
    }

    @Override
    public StringBuilder protectColumn(StringBuilder sb, String name) {
        int n;
        if ((n = name.indexOf(".")) > 0) {
            String table = name.substring(0, n);
            String column = name.substring(n + 1);
            return protectName(sb, table).append(".").append(column);
        }
        return protectName(sb, name);
    }

    protected StringBuilder protectName(
            StringBuilder sb, String name
    ) {
        return sb.append('`').append(name).append("`");
    }

    @Override
    public String protectColumn(String name) {
        if (name.contains("(")) {
            return name;
        }
        int n;
        if ((n = name.indexOf(".")) > 0) {
            String table = name.substring(0, n);
            String column = name.substring(n + 1);
            StringBuilder sb = new StringBuilder();
            protectName(sb, table)
                    .append(".");
            return protectColumn(sb, column).toString();
        }
        return protectName(name);
    }

    @Override
    public StringBuilder protectTable(StringBuilder sb, String name) {
        return protectColumn(sb, name);
    }

    protected void buildDefaultValue(ColumnMeta columnMeta, StringBuilder sb) {
        if (columnMeta.getDefaultValue() instanceof String) {
            sb.append(" DEFAULT '").append(columnMeta.getDefaultValue()).append("'");
        } else {
            if (columnMeta.getDefaultValue() instanceof DefaultValue) {
                sb.append(" DEFAULT ").append(((DefaultValue) columnMeta.getDefaultValue()).getValue());
            } else {
                sb.append(" DEFAULT ").append(columnMeta.getDefaultValue());
            }
        }
    }

    protected String buildCreateTable(TableBuildInfo table) {
        List<String> primaryKeys = new ArrayList<String>(3);
        StringBuilder sb = new StringBuilder();

        for (ColumnMeta columnMeta : table.getColumns()) {
            if (columnMeta.isPrimary()) {
                primaryKeys.add(columnMeta.getName());
            }
        }

        sb.append("CREATE TABLE ");
        if (table.isCreateWhenNotExists()) {
            sb.append("IF NOT EXISTS ");
        }
        protectTable(sb, table.getName());
        sb.append("(\n");
        boolean first = false;
        int index = 0;
        for (ColumnMeta columnMeta : table.getColumns()) {
            sb.append("\t");
            protectColumn(sb, columnMeta.getName());
            sb.append(' ');
            try {
                sb.append(formatColumnType(columnMeta));
            } catch (Exception e) {
                throw new ZoomException("不支持的类型" + columnMeta.getName());
            }


            if (columnMeta.getDefaultValue() != null) {
                buildDefaultValue(columnMeta, sb);
            } else {
                if (columnMeta.isPrimary()) {
                    if (columnMeta.isAuto()) {
                        sb.append(" PRIMARY KEY");
                        sb.append(" auto_increment".toUpperCase());
                    } else {
                        //single primary key
                        if (primaryKeys.size() == 1) {
                            sb.append(" PRIMARY KEY");
                        }
                    }
                } else {
                    sb.append(columnMeta.isNullable()
                            ? " NULL"
                            : " NOT NULL");
                }
            }

            createColumnComment(sb, columnMeta);

            if (index < table.getColumns().size() - 1) {
                sb.append(",");
            }

            if (index == table.getColumns().size() - 1) {
                break;
            }
            sb.append("\n");
            ++index;
        }

        if (primaryKeys.size() > 1) {
            first = true;

            sb.append(",\n\tPRIMARY KEY (");
            for (String key : primaryKeys) {
                if (first) {
                    first = false;
                } else {
                    sb.append(",");
                }
                sb.append(key);
            }
            sb.append(")\n");

        } else {
            sb.append("\n");
        }

        sb.append(")");

        if (table.engine != null) {
            sb.append(" ENGINE=").append(table.engine);
        }

        sb.append(" charset=utf8");

        createTableComment(sb, table);

        return sb.toString();
    }

    protected void createColumnComment(StringBuilder sb, ColumnMeta columnMeta) {
        if (!StringUtils.isEmpty(columnMeta.getComment())) {
            sb.append(" COMMENT '")
                    .append(columnMeta.getComment()).append("'");
        }


    }

    protected void createTableComment(StringBuilder sb, TableBuildInfo table) {
        if (StringUtils.isEmpty(table.getComment())) {
            return;
        }
        sb.append(" COMMENT='").append(table.getComment()).append("'");
    }


}
